package com.dkd.sbtz.service.impl;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;

import com.dkd.common.config.RuoYiConfig;
import com.dkd.common.core.domain.model.LoginUser;
import com.dkd.common.exception.ServiceException;
import com.dkd.common.utils.DateUtils;
import com.dkd.common.utils.SecurityUtils;
import com.dkd.common.utils.StringUtils;
import lombok.RequiredArgsConstructor;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.dkd.sbtz.mapper.AssetFileMapper;
import com.dkd.sbtz.domain.AssetFile;
import com.dkd.sbtz.service.IAssetFileService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;

/**
 * 文件管理Service业务层处理
 * 
 * @author xzj
 * @date 2025-09-23
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class AssetFileServiceImpl implements IAssetFileService 
{
    @Autowired
    private AssetFileMapper assetFileMapper;

    // 若依：文件根路径，如 D:/ruoyi/uploadPath
//    @Value("${ruoyi.profile}")
//    private String profile;
    String profile = RuoYiConfig.getProfile();

    private static final DateTimeFormatter DAY = DateTimeFormatter.ofPattern("yyyyMMdd");

    /**
     * 查询文件管理
     * 
     * @param id 文件管理主键
     * @return 文件管理
     */
    @Override
    public AssetFile selectAssetFileById(Long id)
    {
        return assetFileMapper.selectAssetFileById(id);
    }

    /**
     * 查询文件管理列表
     * 
     * @param assetFile 文件管理
     * @return 文件管理
     */
    @Override
    public List<AssetFile> selectAssetFileList(AssetFile assetFile)
    {
        return assetFileMapper.selectAssetFileList(assetFile);
    }

    /**
     * 新增文件管理
     * 
     * @param assetFile 文件管理
     * @return 结果
     */
    @Override
    public int insertAssetFile(AssetFile assetFile)
    {
        assetFile.setCreateTime(DateUtils.getNowDate());
        return assetFileMapper.insertAssetFile(assetFile);
    }

    /**
     * 修改文件管理
     * 
     * @param assetFile 文件管理
     * @return 结果
     */
    @Override
    public int updateAssetFile(AssetFile assetFile)
    {
        return assetFileMapper.updateAssetFile(assetFile);
    }

    /**
     * 批量删除文件管理
     * 
     * @param ids 需要删除的文件管理主键
     * @return 结果
     */
    @Override
    public int deleteAssetFileByIds(Long[] ids)
    {
        return assetFileMapper.deleteAssetFileByIds(ids);
    }

    /**
     * 删除文件管理信息
     * 
     * @param id 文件管理主键
     * @return 结果
     */
    @Override
    public int deleteAssetFileById(Long id)
    {
        return assetFileMapper.deleteAssetFileById(id);
    }


    @Override
    public void uploadBatch(List<MultipartFile> files, List<String> relativePaths) {
        if (files == null || files.isEmpty()) return;

        String profile = RuoYiConfig.getProfile();
        String dateDir = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); // yyyyMMdd
        String subDir  = "asset_file/" + dateDir;
        Path saveDir   = Paths.get(profile, subDir);
        try { Files.createDirectories(saveDir); } catch (IOException ignored) {}

        List<AssetFile> toSave = new ArrayList<>();
        for (int i = 0; i < files.size(); i++) {
            MultipartFile mf = files.get(i);
            if (mf.isEmpty()) continue;

            String original = Optional.ofNullable(mf.getOriginalFilename()).orElse("unnamed");
            String ext = getExtensionSafe(original);
            String uuid = UUID.randomUUID().toString().replace("-", "");
            String newName = StringUtils.isNotBlank(ext) ? (uuid + "." + ext) : uuid;

            Path target = saveDir.resolve(newName);
            try (InputStream in = mf.getInputStream()) {
                Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING);
            } catch (IOException ex) {
                throw new RuntimeException("文件保存失败: " + original, ex);
            }

            String storageUrl  = "/profile/" + subDir + "/" + newName; // 浏览器可访问
            String storagePath = subDir + "/" + newName;                // 相对 profile

            AssetFile af = new AssetFile();
            af.setOriginalName(original);
            af.setExt(ext);
            af.setMimeType(mf.getContentType());
            af.setSizeBytes(mf.getSize());
            af.setSha256(calcSha256(target)); // 计算哈希
            af.setStoragePath(storagePath);
            af.setStorageUrl(storageUrl);
            af.setPreviewType(detectPreviewType(af.getMimeType(), ext));
            // 如果是图片，读取宽高
            if (af.getPreviewType().equals("image")) {
                try {
                    BufferedImage img = ImageIO.read(target.toFile());
                    if (img != null) {
                        af.setImageWidth(img.getWidth());
                        af.setImageHeight(img.getHeight());
                    }
                } catch (Exception ignored) {}
            }
            // 如果是 PDF，读页数（可选：需要引入 org.apache.pdfbox:pdfbox）
            if (af.getPreviewType().equals("pdf")) {
                try (PDDocument doc = PDDocument.load(target.toFile())) {
                    af.setPdfPages(doc.getNumberOfPages());
                } catch (Exception ignored) {}
            }

            // 额外信息：把前端的 relativePath 放进 extra_json
            String rel = getOrEmpty(relativePaths, i);
            if (StringUtils.isNotBlank(rel)) {
                af.setExtraJson("{\"relativePath\":\"" + rel.replace("\"","\\\"") + "\"}");
            }

            af.setBizTag(null); // 需要的话你可以通过请求参数传值
            af.setCreateBy(SecurityUtils.getUsername());
            af.setRemark(null);
            toSave.add(af);
        }
        if (!toSave.isEmpty()) {
            assetFileMapper.insertBatch(toSave);
        }
    }

    // 放到 ServiceImpl 里（或单独 Utils 类）
    private static String getExtensionSafe(String filename) {
        if (filename == null) return "";
        int dot = filename.lastIndexOf('.');
        if (dot < 0 || dot == filename.length() - 1) return "";
        return filename.substring(dot + 1).toLowerCase(java.util.Locale.ROOT);
    }

    private String getOrEmpty(List<String> list, int idx) {
        if (list == null || idx >= list.size()) return "";
        return Optional.ofNullable(list.get(idx)).orElse("");
    }

    @Override
    public void softDelete(Long id) {
        AssetFile one = assetFileMapper.selectById(id);
        // 物理文件也可顺便删（可选）
        if (one != null && StringUtils.isNotEmpty(one.getStoragePath())) {
            try { Files.deleteIfExists(Paths.get(RuoYiConfig.getProfile(), one.getStoragePath())); }
            catch (IOException ignored) {}
        }
        assetFileMapper.softDeleteById(id, SecurityUtils.getUsername());
    }

    /** 计算 SHA-256 */
    private String calcSha256(Path path) {
        try (InputStream in = Files.newInputStream(path)) {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] buf = new byte[8192];
            int n;
            while ((n = in.read(buf)) > 0) md.update(buf, 0, n);
            byte[] d = md.digest();
            StringBuilder sb = new StringBuilder();
            for (byte b : d) sb.append(String.format("%02x", b));
            return sb.toString();
        } catch (Exception e) {
            return null;
        }
    }

    /** 预览类型推断 */
    private String detectPreviewType(String mime, String ext) {
        String m = StringUtils.defaultString(mime).toLowerCase();
        String e = StringUtils.defaultString(ext).toLowerCase();
        if (m.startsWith("image/") || List.of("png","jpg","jpeg","gif","webp","bmp").contains(e)) return "image";
        if (m.equals("application/pdf") || "pdf".equals(e)) return "pdf";
        if (m.startsWith("text/") || List.of("txt","md","csv","log").contains(e)) return "text";
        if (List.of("doc","docx","xls","xlsx","ppt","pptx").contains(e)) return "office";
        if (m.startsWith("audio/")) return "audio";
        if (m.startsWith("video/")) return "video";
        return "binary";
    }

    @Override
    public void replaceFile(Long id, MultipartFile file, String relativePath) {
        AssetFile old = assetFileMapper.selectById(id);
        if (old == null || old.getIsDelete() != null && old.getIsDelete() == 1) {
            throw new ServiceException("记录不存在或已删除");
        }
        if (file == null || file.isEmpty()) {
            throw new ServiceException("文件为空");
        }

        String profile = RuoYiConfig.getProfile();
        String dateDir = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); // yyyyMMdd
        String subDir  = "asset_file/" + dateDir;
        Path saveDir   = Paths.get(profile, subDir);
        try { Files.createDirectories(saveDir); } catch (IOException ignore) {}

        // 生成新文件名并保存
        String original = Optional.ofNullable(file.getOriginalFilename()).orElse("unnamed");
        String ext = getExtensionSafe(original);
        String uuid = UUID.randomUUID().toString().replace("-", "");
        String newName = ext.isEmpty() ? uuid : (uuid + "." + ext);
        Path target = saveDir.resolve(newName);
        try (InputStream in = file.getInputStream()) {
            Files.copy(in, target, StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException ex) {
            log.error("文件保存失败: {}; path={}", original, target, ex);
            throw new ServiceException("文件保存失败: " + original);
        }

        // 旧物理文件可删除（可选）
        if (StringUtils.isNotEmpty(old.getStoragePath())) {
            try { Files.deleteIfExists(Paths.get(profile, old.getStoragePath())); } catch (IOException ignore) {}
        }

        // 计算属性并更新 DB
        String storageUrl  = "/profile/" + subDir + "/" + newName;
        String storagePath =  subDir + "/" + newName;

        AssetFile upd = new AssetFile();
        upd.setId(id);
        upd.setOriginalName(original);
        upd.setExt(ext);
        upd.setMimeType(file.getContentType());
        upd.setSizeBytes(file.getSize());
        upd.setSha256(calcSha256(target));
        upd.setStoragePath(storagePath);
        upd.setStorageUrl(storageUrl);
        upd.setPreviewType(detectPreviewType(upd.getMimeType(), ext));
        // 图片宽高
        if ("image".equals(upd.getPreviewType())) {
            try {
                javax.imageio.ImageIO.setUseCache(false);
                java.awt.image.BufferedImage img = javax.imageio.ImageIO.read(target.toFile());
                if (img != null) { upd.setImageWidth(img.getWidth()); upd.setImageHeight(img.getHeight()); }
            } catch (Exception ignore) {}
        }
        // PDF 页数（如需，按前述加 pdfbox 依赖）
        // if ("pdf".equalsIgnoreCase(ext)) { ... upd.setPdfPages(...); }

        // 记录相对路径（可选）
        if (StringUtils.isNotBlank(relativePath)) {
            String extra = StringUtils.defaultString(old.getExtraJson(), "{}");
            // 简单拼装（生产可用 JSON lib 合并）
            upd.setExtraJson("{\"relativePath\":\"" + relativePath.replace("\"","\\\"") + "\"}");
        } else {
            upd.setExtraJson(old.getExtraJson());
        }

        upd.setUpdateBy(SecurityUtils.getUsername());
        assetFileMapper.updateFileById(upd); // 只更新非空字段（见 Mapper）
    }



    @Transactional(rollbackFor = Exception.class)
    @Override
    public List<AssetFile> saveFiles(List<MultipartFile> files, List<String> relativePaths, String bizTag, String remark) {
        String day = LocalDate.now().format(DAY);

        List<AssetFile> result = new ArrayList<>();
        for (int i = 0; i < files.size(); i++) {
            MultipartFile mf = files.get(i);
            String rel = (relativePaths != null && i < relativePaths.size()) ? relativePaths.get(i) : "";

            try {
                // 1) 基础元信息
                String originalName = Objects.requireNonNullElse(mf.getOriginalFilename(), "file");
                String ext = getExt(originalName);
                String mimeType = Objects.requireNonNullElse(mf.getContentType(), "application/octet-stream");
                byte[] data = mf.getBytes();
                long sizeBytes = data.length;
                String sha256 = sha256Hex(data);

                // 2) 目录与文件名
                String cleanRelDir = extractDir(cleanRelative(rel)); // 仅保留相对目录部分
                String uuid = UUID.randomUUID().toString().replace("-", "");
                String fileName = uuid + (ext.isEmpty() ? "" : "." + ext);

                // 3) 物理路径与 URL
                Path base = Paths.get(profile, "asset_file", day);                      // 物理根
                Path dir = cleanRelDir.isEmpty() ? base : base.resolve(cleanRelDir);    // 物理目录
                Files.createDirectories(dir);
                Path target = dir.resolve(fileName);

                // 4) 写盘
                try (OutputStream out = Files.newOutputStream(target, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
                    out.write(data);
                }

                // 5) 预览能力与维度
                String previewType = resolvePreviewType(mimeType, ext);
                Integer imageWidth = null, imageHeight = null, pdfPages = null;
                if ("image".equals(previewType)) {
                    try (InputStream in = new ByteArrayInputStream(data)) {
                        BufferedImage img = ImageIO.read(in);
                        if (img != null) {
                            imageWidth = img.getWidth();
                            imageHeight = img.getHeight();
                        }
                    } catch (Exception ignore) {}
                } else if ("pdf".equals(previewType)) {
                    try (PDDocument doc = PDDocument.load(data)) {
                        pdfPages = doc.getNumberOfPages();
                    } catch (Exception ignore) {}
                }

                // 6) DB 字段（storagePath 存相对，storageUrl 走 /profile/**）
                String relativePathForDb = Paths.get("asset_file", day)
                        .resolve(cleanRelDir.isEmpty() ? "" : cleanRelDir)
                        .resolve(fileName).toString().replace("\\", "/");
                String storagePath = "/" + relativePathForDb;                  // /asset_file/20250924/dirA/b/uuid.jpg
                String storageUrl  = "/profile" + storagePath;                 // /profile/asset_file/20250924/dirA/b/uuid.jpg

                AssetFile row = new AssetFile();
                row.setOriginalName(originalName);
                row.setExt(ext);
                row.setMimeType(mimeType);
                row.setSizeBytes(sizeBytes);
                row.setSha256(sha256);
                row.setStoragePath(storagePath);
                row.setStorageUrl(storageUrl);
                row.setPreviewType(previewType);
                row.setImageWidth(imageWidth);
                row.setImageHeight(imageHeight);
                row.setPdfPages(pdfPages);
                row.setBizTag(StringUtils.defaultString(bizTag, ""));
                row.setRemark(StringUtils.defaultString(remark, ""));
                row.setIsDelete(0);
                row.setCreateBy(getLoginNameSafely());
                row.setUpdateBy(getLoginNameSafely());
                row.setCreateTime(new Date());
                row.setUpdateTime(new Date());

                assetFileMapper.insert(row); // 或者你有 insertBatch 也可以先收集后统一 batch

                result.add(row);
            } catch (IOException e) {
                log.error("保存文件失败: idx={}, name={}", i, files.get(i).getOriginalFilename(), e);
                throw new ServiceException("文件保存失败：" + files.get(i).getOriginalFilename());
            }
        }
        return result;
    }


    // --- helpers ---

    private String getExt(String name) {
        int i = name.lastIndexOf('.');
        return (i >= 0 && i < name.length() - 1) ? name.substring(i + 1).toLowerCase() : "";
    }

    private String sha256Hex(byte[] data) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] d = md.digest(data);
            StringBuilder sb = new StringBuilder(d.length * 2);
            for (byte b : d) sb.append(String.format("%02x", b));
            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException(e);
        }
    }

    private String cleanRelative(String s) {
        if (StringUtils.isBlank(s)) return "";
        s = s.replace("\\", "/");
        s = s.replaceAll("^/+", "").replaceAll("/+", "/");
        // 防止路径穿越
        s = s.replace("..", "");
        return s;
    }

    /** 只取目录部分（去掉文件名） */
    private String extractDir(String rel) {
        if (StringUtils.isBlank(rel)) return "";
        int p = rel.lastIndexOf('/');
        return p > 0 ? rel.substring(0, p) : "";
    }

    private String resolvePreviewType(String mime, String ext) {
        String m = StringUtils.defaultString(mime, "").toLowerCase();
        String e = StringUtils.defaultString(ext, "").toLowerCase();
        if (m.startsWith("image/") || Arrays.asList("png","jpg","jpeg","gif","webp","bmp","svg").contains(e)) return "image";
        if ("application/pdf".equals(m) || "pdf".equals(e)) return "pdf";
        return "none";
    }

    private String getLoginNameSafely() {
        try {
            LoginUser user = SecurityUtils.getLoginUser();
            return user != null && user.getUser() != null ? user.getUser().getUserName() : "system";
        } catch (Exception e) {
            return "system";
        }
    }


}
